import java.util.*;

/**
 * @author LKQ
 * @date 2022/5/15 17:08
 * @description 动态规划 巧妙解法
 * p[j] 为普通字符：匹配的条件是前面的字符匹配，同时 s 中的第 i 个字符和 p 中的第 j 位相同。 即 f(i,j) = f(i - 1, j - 1) && s[i] == p[j] 。
 * p[j] 为 '.'：匹配的条件是前面的字符匹配，s 中的第 i 个字符可以是任意字符。即 f(i,j) = f(i - 1, j - 1) && p[j] == '.'。
 * p[j] 为 '*'：可匹配任意长度的字符，可以匹配 0 个字符、匹配 1 个字符、匹配 2 个字符
 * 3.1. 当匹配为 0 个：f(i,j) = f(i, j - 1)
 * 3.2. 当匹配为 1 个：f(i,j) = f(i - 1, j - 1)
 * 3.3. 当匹配为 2 个：f(i,j) = f(i - 2, j - 1)
 * ...
 * 3.k. 当匹配为 k 个：f(i,j) = f(i - k, j - 1)
 *
 */
public class Solution2 {
    public static void main(String[] args) {

    }
    public boolean isMatch(String ss, String pp) {
        int n = ss.length(), m = pp.length();
        // 技巧：往原字符头部插入空格，这样得到 char 数组是从 1 开始，而且可以使得 f[0][0] = true，可以将 true 这个结果滚动下去
        ss = " " + ss;
        pp = " " + pp;
        char[] s = ss.toCharArray();
        char[] p = pp.toCharArray();
        // f(i,j) 代表考虑 s 中的 1~i 字符和 p 中的 1~j 字符 是否匹配
        boolean[][] f = new boolean[n + 1][m + 1];
        f[0][0] = true;
        for (int i = 0; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                if (p[j] == '*') {
                    // *号，这里 f[i][j] = f[i][j-1] (不使用*号) || f[i-1][j-1] || f[i-2][j-1]... || f[i-k][j-1] (使用*号）
                    // 也就是 *号匹配任意 k 个字符，但是并不是需要枚举 k， 因为 f[i-1][j] = f[i-1][j-1] || f[i-2][j-1] || ... || f[i-k][j-1]
                    // 可以发现 f[i-1][j]等价与上面的后半部分， 所有 f[i][j] = f[i][j-1] || f[i-1][j]
                    f[i][j] = f[i][j - 1] || (i - 1 >= 0 && f[i - 1][j]);
                } else {
                    f[i][j] = i - 1 >= 0 && f[i - 1][j - 1] && (s[i] == p[j] || p[j] == '?');
                }
            }
        }
        return f[n][m];
    }
}
